/*
JSNES, based on Jamie Sanders' vNES
Copyright (C) 2010 Ben Firshman

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

JSNES.DummyUI = function (nes) {
  this.nes = nes;
  this.enable = function () {};
  this.updateStatus = function () {};
  this.writeAudio = function () {};
  this.writeFrame = function () {};
};

if (typeof jQuery !== "undefined") {
  (function ($) {
    $.fn.JSNESUI = function (roms) {
      var parent = this;
      var UI = function (nes) {
        var self = this;
        self.nes = nes;

        /*
         * Create UI
         */
        self.status = $('<p class="nes-status">Booting up...</p>').appendTo(
          self.root
        );
        self.root = $("<div></div>");
        self.screen = $(
          '<canvas class="nes-screen" width="256" height="240"></canvas>'
        ).appendTo(self.root);

        if (!self.screen[0].getContext) {
          parent.html(
            "Your browser doesn't support the <code>&lt;canvas&gt;</code> tag. Try Google Chrome, Safari, Opera or Firefox!"
          );
          return;
        }
        self.romContainer = $('<div class="nes-roms"></div>').appendTo(
          self.root
        );
        self.romSelect = $("<select></select>").appendTo(self.romContainer);
        self.controls = $('<div class="nes-controls"></div>').appendTo(
          self.root
        );
        self.buttons = {
          pause: $(
            '<input type="button" value="暂停" class="nes-pause" disabled="disabled">'
          ).appendTo(self.controls),
          restart: $(
            '<input type="button" value="重启" class="nes-restart" disabled="disabled">'
          ).appendTo(self.controls),
          sound: $(
            '<input type="button" value="开启声音" class="nes-enablesound">'
          ).appendTo(self.controls),
          zoom: $(
            '<input type="button" value="放大" class="nes-zoom">'
          ).appendTo(self.controls),
        };
        self.root.appendTo(parent);

        /*
         * ROM loading
         */
        self.romSelect.change(function () {
          self.loadROM();
        });

        /*
         * Buttons
         */
        self.buttons.pause.click(function () {
          if (self.nes.isRunning) {
            self.nes.stop();
            self.updateStatus("Paused");
            self.buttons.pause.attr("value", "继续");
          } else {
            self.nes.start();
            self.buttons.pause.attr("value", "暂停");
          }
        });

        self.buttons.restart.click(function () {
          self.nes.reloadRom();
          self.nes.start();
        });

        self.buttons.sound.click(function () {
          if (self.nes.opts.emulateSound) {
            self.nes.opts.emulateSound = false;
            self.buttons.sound.attr("value", "打开声音");
          } else {
            self.nes.opts.emulateSound = true;
            self.buttons.sound.attr("value", "关闭声音");

            var source = self.audio.createBufferSource();
            source.connect(self.audio.destination); // Output to sound
            source.start();
          }
        });

        self.zoomed = false;
        $(".nes-screen").css({
          "max-height": document.documentElement.clientHeight,
        });
        $(window).bind("resize", function () {
          $(".nes-screen").css({
            "max-height": document.documentElement.clientHeight,
          });
        });
        if (/(IPHONE|IPAD|ANDROID)/i.test(navigator.userAgent)) {
          $("#pc-controlls").hide();
        } else {
          $(".nes-zoom").hide();
          $("#mobile-controlls").hide();
          $(".shang").removeClass("anim_m").addClass("pc");
        }

        self.buttons.zoom.click(function () {
          if (self.zoomed) {
            //                        self.screen.animate({
            //                            width: '512px',
            //                            height: '480px'
            //                        });
            $("body").addClass("放大");
            if (document.documentElement.clientHeight < screen.availHeight) {
              $("body").css({
                width: document.documentElement.clientHeight,
              });
            }
            $(".big .nes-screen").css({
              height: document.documentElement.clientWidth,
              width: "auto",
            });
            self.buttons.zoom.attr("value", "放大");
            self.zoomed = true;
          } else {
            //                        self.screen.animate({
            //                            width: '256px',
            //                            height: '240px'
            //                        });
            self.buttons.zoom.attr("value", "缩小");
            $("body").removeClass("big");
            $(".nes-screen").css({
              height: "auto",
              width: "100%",
              "max-width": document.documentElement.clientWidth,
              "max-height": document.documentElement.clientHeight,
            });
            $("body").css({
              width: "auto",
            });
            self.zoomed = false;
          }
        });

        /*
         * Lightgun experiments with mouse
         * (Requires jquery.dimensions.js)
         */
        if ($.offset) {
          self.screen
            .mousedown(function (e) {
              if (self.nes.mmap) {
                self.nes.mmap.mousePressed = true;
                // FIXME: does not take into account zoom
                self.nes.mmap.mouseX = e.pageX - self.screen.offset().left;
                self.nes.mmap.mouseY = e.pageY - self.screen.offset().top;
              }
            })
            .mouseup(function () {
              setTimeout(function () {
                if (self.nes.mmap) {
                  self.nes.mmap.mousePressed = false;
                  self.nes.mmap.mouseX = 0;
                  self.nes.mmap.mouseY = 0;
                }
              }, 500);
            });
        }

        if (typeof roms != "undefined") {
          self.setRoms(roms);
        }

        /*
         * Canvas
         */
        self.canvasContext = self.screen[0].getContext("2d");

        if (!self.canvasContext.getImageData) {
          parent.html(
            "Your browser doesn't support writing pixels directly to the <code>&lt;canvas&gt;</code> tag. Try the latest versions of Google Chrome, Safari, Opera or Firefox!"
          );
          return;
        }

        self.canvasImageData = self.canvasContext.getImageData(0, 0, 256, 240);
        self.resetCanvas();

        /*
         * Keyboard
         */
        $(document)
          .bind("keydown", function (evt) {
            self.nes.keyboard.keyDown(evt);
          })
          .bind("keyup", function (evt) {
            self.nes.keyboard.keyUp(evt);
          })
          .bind("keypress", function (evt) {
            self.nes.keyboard.keyPress(evt);
          });

        $("#controls-direction").bind("touchstart", function (e) {
          handleDirection(e);
          e.preventDefault();
        });
        $("#controls-direction").bind("gesturestart", function (e) {
          handleDirection(e);
          e.preventDefault();
        });
        $("#controls-direction").bind("touchmove", function (e) {
          handleDirection(e);
        });

        function handleDirection(e) {
          var myLocation = e.originalEvent.changedTouches[0];
          var realTarget = document.elementFromPoint(
            myLocation.clientX,
            myLocation.clientY
          );
          if ($(realTarget).hasClass("leftup")) {
            $("#controls-direction .left").addClass("active");
            $("#controls-direction .up").addClass("active");
            $("#controls-direction .down").removeClass("active");
            $("#controls-direction .right").removeClass("active");
            self.nes.keyboard.keyDown({
              keyCode: 37,
            });
            self.nes.keyboard.keyDown({
              keyCode: 38,
            });
            self.nes.keyboard.keyUp({
              keyCode: 39,
            });
            self.nes.keyboard.keyUp({
              keyCode: 40,
            });
          } else if ($(realTarget).hasClass("up")) {
            $("#controls-direction .left").removeClass("active");
            $("#controls-direction .up").addClass("active");
            $("#controls-direction .down").removeClass("active");
            $("#controls-direction .right").removeClass("active");
            self.nes.keyboard.keyUp({
              keyCode: 37,
            });
            self.nes.keyboard.keyDown({
              keyCode: 38,
            });
            self.nes.keyboard.keyUp({
              keyCode: 39,
            });
            self.nes.keyboard.keyUp({
              keyCode: 40,
            });
          } else if ($(realTarget).hasClass("rightup")) {
            $("#controls-direction .left").removeClass("active");
            $("#controls-direction .up").addClass("active");
            $("#controls-direction .down").removeClass("active");
            $("#controls-direction .right").addClass("active");
            self.nes.keyboard.keyUp({
              keyCode: 37,
            });
            self.nes.keyboard.keyDown({
              keyCode: 38,
            });
            self.nes.keyboard.keyDown({
              keyCode: 39,
            });
            self.nes.keyboard.keyUp({
              keyCode: 40,
            });
          } else if ($(realTarget).hasClass("left")) {
            $("#controls-direction .left").addClass("active");
            $("#controls-direction .up").removeClass("active");
            $("#controls-direction .down").removeClass("active");
            $("#controls-direction .right").removeClass("active");
            self.nes.keyboard.keyDown({
              keyCode: 37,
            });
            self.nes.keyboard.keyUp({
              keyCode: 38,
            });
            self.nes.keyboard.keyUp({
              keyCode: 39,
            });
            self.nes.keyboard.keyUp({
              keyCode: 40,
            });
          } else if ($(realTarget).hasClass("center")) {
            $("#controls-direction .left").removeClass("active");
            $("#controls-direction .up").removeClass("active");
            $("#controls-direction .down").removeClass("active");
            $("#controls-direction .right").removeClass("active");
            self.nes.keyboard.keyUp({
              keyCode: 37,
            });
            self.nes.keyboard.keyUp({
              keyCode: 38,
            });
            self.nes.keyboard.keyUp({
              keyCode: 39,
            });
            self.nes.keyboard.keyUp({
              keyCode: 40,
            });
          } else if ($(realTarget).hasClass("right")) {
            $("#controls-direction .left").removeClass("active");
            $("#controls-direction .up").removeClass("active");
            $("#controls-direction .down").removeClass("active");
            $("#controls-direction .right").addClass("active");
            self.nes.keyboard.keyUp({
              keyCode: 37,
            });
            self.nes.keyboard.keyUp({
              keyCode: 38,
            });
            self.nes.keyboard.keyDown({
              keyCode: 39,
            });
            self.nes.keyboard.keyUp({
              keyCode: 40,
            });
          } else if ($(realTarget).hasClass("leftdown")) {
            $("#controls-direction .left").addClass("active");
            $("#controls-direction .up").removeClass("active");
            $("#controls-direction .down").addClass("active");
            $("#controls-direction .right").removeClass("active");
            self.nes.keyboard.keyDown({
              keyCode: 37,
            });
            self.nes.keyboard.keyUp({
              keyCode: 38,
            });
            self.nes.keyboard.keyUp({
              keyCode: 39,
            });
            self.nes.keyboard.keyDown({
              keyCode: 40,
            });
          } else if ($(realTarget).hasClass("down")) {
            $("#controls-direction .left").removeClass("active");
            $("#controls-direction .up").removeClass("active");
            $("#controls-direction .down").addClass("active");
            $("#controls-direction .right").removeClass("active");
            self.nes.keyboard.keyUp({
              keyCode: 37,
            });
            self.nes.keyboard.keyUp({
              keyCode: 38,
            });
            self.nes.keyboard.keyUp({
              keyCode: 39,
            });
            self.nes.keyboard.keyDown({
              keyCode: 40,
            });
          } else if ($(realTarget).hasClass("rightdown")) {
            $("#controls-direction .left").removeClass("active");
            $("#controls-direction .up").removeClass("active");
            $("#controls-direction .down").addClass("active");
            $("#controls-direction .right").addClass("active");
            self.nes.keyboard.keyUp({
              keyCode: 37,
            });
            self.nes.keyboard.keyUp({
              keyCode: 38,
            });
            self.nes.keyboard.keyDown({
              keyCode: 39,
            });
            self.nes.keyboard.keyDown({
              keyCode: 40,
            });
          }
        }
        $("#controls-direction").bind("touchend", function (e) {
          $("#controls-direction .left").removeClass("active");
          $("#controls-direction .up").removeClass("active");
          $("#controls-direction .down").removeClass("active");
          $("#controls-direction .right").removeClass("active");
          self.nes.keyboard.keyUp({
            keyCode: 37,
          });
          self.nes.keyboard.keyUp({
            keyCode: 38,
          });
          self.nes.keyboard.keyUp({
            keyCode: 39,
          });
          self.nes.keyboard.keyUp({
            keyCode: 40,
          });
        });

        $("#joystick_btn_up").bind("touchstart", function (e) {
          self.nes.keyboard.keyDown({
            keyCode: 87,
          });
          e.preventDefault();
        });
        $("#joystick_btn_up").bind("touchend", function (e) {
          self.nes.keyboard.keyUp({
            keyCode: 87,
          });
          e.preventDefault();
        });
        $("#joystick_btn_down").bind("touchstart", function (e) {
          self.nes.keyboard.keyDown({
            keyCode: 83,
          });
          e.preventDefault();
        });
        $("#joystick_btn_down").bind("touchend", function (e) {
          self.nes.keyboard.keyUp({
            keyCode: 83,
          });
          e.preventDefault();
        });
        $("#joystick_btn_left").bind("touchstart", function (e) {
          self.nes.keyboard.keyDown({
            keyCode: 65,
          });
          e.preventDefault();
        });
        $("#joystick_btn_left").bind("touchend", function (e) {
          self.nes.keyboard.keyUp({
            keyCode: 65,
          });
          e.preventDefault();
        });
        $("#joystick_btn_right").bind("touchstart", function (e) {
          console.log("right");
          self.nes.keyboard.keyDown({
            keyCode: 68,
          });
          e.preventDefault();
        });
        $("#joystick_btn_right").bind("touchend", function (e) {
          self.nes.keyboard.keyUp({
            keyCode: 68,
          });
          e.preventDefault();
        });
        $("#joystick_btn_A").bind("touchstart", function (e) {
          console.log("a");
          self.nes.keyboard.keyDown({
            keyCode: 74,
          });
          e.preventDefault();
        });
        $("#joystick_btn_A").bind("touchend", function (e) {
          self.nes.keyboard.keyUp({
            keyCode: 74,
          });
          e.preventDefault();
        });
        $("#joystick_btn_B").bind("touchstart", function (e) {
          console.log("b");
          self.nes.keyboard.keyDown({
            keyCode: 75,
          });
          e.preventDefault();
        });
        $("#joystick_btn_B").bind("touchend", function (e) {
          self.nes.keyboard.keyUp({
            keyCode: 75,
          });
          e.preventDefault();
        });
        $("#joystick_btn_select").bind("touchstart", function (e) {
          self.nes.keyboard.keyDown({
            keyCode: 17,
          });
          $("#joystick_btn_select").addClass("active");
          e.preventDefault();
        });
        $("#joystick_btn_select").bind("touchend", function (e) {
          self.nes.keyboard.keyUp({
            keyCode: 17,
          });
          $("#joystick_btn_select").removeClass("active");
          e.preventDefault();
        });
        $("#joystick_btn_start").bind("touchstart", function (e) {
          self.nes.keyboard.keyDown({
            keyCode: 13,
          });
          $("#joystick_btn_start").addClass("active");
          e.preventDefault();
        });
        $("#joystick_btn_start").bind("touchend", function (e) {
          self.nes.keyboard.keyUp({
            keyCode: 13,
          });
          $("#joystick_btn_start").removeClass("active");
          e.preventDefault();
        });

        $("#controls-fire").bind("touchstart", function (e) {
          handleFire(e);
          e.preventDefault();
        });
        $("#controls-fire").bind("gesturestart", function (e) {
          handleFire(e, true);
          e.preventDefault();
        });
        $("#controls-fire").bind("touchmove", function (e) {
          handleFire(e);
          e.preventDefault();
        });
        $("#controls-fire").bind("touchend", function (e) {
          clearInterval(self.interval);
          $("#controls-fire .a").removeClass("active");
          $("#controls-fire .b").removeClass("active");
          self.nes.keyboard.keyUp({
            keyCode: 88,
          });
          self.nes.keyboard.keyUp({
            keyCode: 90,
          });
          e.preventDefault();
        });
        $("#controls-turbofire").bind("touchstart", function (e) {
          handleFire(e, true);
          e.preventDefault();
        });
        $("#controls-turbofire").bind("gesturestart", function (e) {
          handleFire(e, true);
          e.preventDefault();
        });
        $("#controls-turbofire").bind("touchmove", function (e) {
          handleFire(e, true);
          e.preventDefault();
        });
        $("#controls-turbofire").bind("touchend", function (e) {
          clearInterval(self.interval);
          $("#controls-turbofire .a").removeClass("active");
          $("#controls-turbofire .b").removeClass("active");
          self.nes.keyboard.keyUp({
            keyCode: 74,
          });
          self.nes.keyboard.keyUp({
            keyCode: 75,
          });
          e.preventDefault();
        });

        function handleFire(e, turbo) {
          var parent = $("#controls-fire");
          if (turbo) {
            parent = $("#controls-turbofire");
          }
          var myLocation = e.originalEvent.changedTouches[0];
          var realTarget = document.elementFromPoint(
            myLocation.clientX,
            myLocation.clientY
          );
          if ($(realTarget).hasClass("a")) {
            $(".a", parent).addClass("active");
            $(".b", parent).removeClass("active");
            clearInterval(self.interval);
            if (turbo) {
              self.interval = setInterval(() => {
                console.log("aa");
                self.nes.keyboard.keyDown({
                  keyCode: 74,
                });
                setTimeout(() => {
                  self.nes.keyboard.keyUp({
                    keyCode: 74,
                  });
                }, 49);
              }, 50);
            } else {
              self.nes.keyboard.keyDown({
                keyCode: 74,
              });
            }
          } else if ($(realTarget).hasClass("b")) {
            $(".a", parent).removeClass("active");
            $(".b", parent).addClass("active");
            clearInterval(self.interval);
            if (turbo) {
              self.interval = setInterval(() => {
                console.log("bb");
                self.nes.keyboard.keyDown({
                  keyCode: 75,
                });
                setTimeout(() => {
                  self.nes.keyboard.keyUp({
                    keyCode: 75,
                  });
                }, 49);
              }, 50);
            } else {
              self.nes.keyboard.keyDown({
                keyCode: 75,
              });
            }
          } else if ($(realTarget).hasClass("c")) {
            $(".a", parent).addClass("active");
            $(".b", parent).addClass("active");
            clearInterval(self.interval);
            if (turbo) {
              self.interval = setInterval(function () {
                self.nes.keyboard.keyDown({
                  keyCode: 74,
                });
                self.nes.keyboard.keyDown({
                  keyCode: 75,
                });
                setTimeout(() => {
                  self.nes.keyboard.keyUp({
                    keyCode: 74,
                  });
                  self.nes.keyboard.keyUp({
                    keyCode: 75,
                  });
                }, 49);
              }, 50);
            } else {
              self.nes.keyboard.keyDown({
                keyCode: 74,
              });
              self.nes.keyboard.keyDown({
                keyCode: 75,
              });
            }
          } else {
            clearInterval(self.interval);
            $(".a", parent).removeClass("active");
            $(".b", parent).removeClass("active");
            self.nes.keyboard.keyUp({
              keyCode: 88,
            });
            self.nes.keyboard.keyUp({
              keyCode: 90,
            });
          }
        }

        /*
         * Sound
         */
        // Workaround prefixed naming used in Safary 8-9
        //                self.dynamicaudio = new DynamicAudio({
        //                    swf: nes.opts.swfPath+'dynamicaudio.swf'
        //                });
        window.AudioContext = window.webkitAudioContext || window.AudioContext;
        try {
          self.audio = new AudioContext();
        } catch (e) {
          // lets fallback to Flash (for Internet Explorer 8-11)
          console.error(e);
          self.dynamicaudio = new DynamicAudio({
            swf: nes.opts.swfPath + "dynamicaudio.swf",
          });
        }
      };

      UI.prototype = {
        loadROM: function () {
          var self = this;
          self.updateStatus("Downloading...");
          $.ajax({
            url: escape(self.romSelect.val()),
            xhr: function () {
              var xhr = $.ajaxSettings.xhr();
              if (typeof xhr.overrideMimeType !== "undefined") {
                // Download as binary
                xhr.overrideMimeType("text/plain; charset=x-user-defined");
              }
              self.xhr = xhr;
              return xhr;
            },
            complete: function (xhr, status) {
              var i, data;
              if (JSNES.Utils.isIE()) {
                var charCodes = JSNESBinaryToArray(xhr.responseBody).toArray();
                data = String.fromCharCode.apply(undefined, charCodes);
              } else {
                data = xhr.responseText;
              }
              self.nes.loadRom(data);
              self.nes.start();
              self.enable();
            },
          });
        },

        resetCanvas: function () {
          this.canvasContext.fillStyle = "black";
          // set alpha to opaque
          this.canvasContext.fillRect(0, 0, 256, 240);

          // Set alpha
          for (var i = 3; i < this.canvasImageData.data.length - 3; i += 4) {
            this.canvasImageData.data[i] = 0xff;
          }
        },

        /*
         *
         * nes.ui.screenshot() --> return <img> element :)
         */
        screenshot: function () {
          var data = this.screen[0].toDataURL("image/png"),
            img = new Image();
          img.src = data;
          return img;
        },

        /*
         * Enable and reset UI elements
         */
        enable: function () {
          this.buttons.pause.attr("disabled", null);
          if (this.nes.isRunning) {
            this.buttons.pause.attr("value", "暂停");
          } else {
            this.buttons.pause.attr("value", "继续");
          }
          this.buttons.restart.attr("disabled", null);
          if (this.nes.opts.emulateSound) {
            this.buttons.sound.attr("value", "关闭声音");
          } else {
            this.buttons.sound.attr("value", "打开声音");
          }
        },

        updateStatus: function (s) {
          this.status.text(s);
        },

        setRoms: function (roms) {
          this.romSelect.children().remove();
          $("<option>选择游戏...</option>").appendTo(this.romSelect);
          for (var groupName in roms) {
            if (roms.hasOwnProperty(groupName)) {
              var optgroup = $("<optgroup></optgroup>").attr(
                "label",
                groupName
              );
              for (var i = 0; i < roms[groupName].length; i++) {
                $("<option>" + roms[groupName][i][0] + "</option>")
                  .attr("value", roms[groupName][i][1])
                  .appendTo(optgroup);
              }
              this.romSelect.append(optgroup);
            }
          }
        },

        writeAudio: function (samples) {
          //return this.dynamicaudio.writeInt(samples);
          // Use fallback if available and return early
          if (this.dynamicaudio) {
            return this.dynamicaudio.writeInt(samples);
          }
          // Create output buffer (planar buffer format)
          var buffer = this.audio.createBuffer(
            2,
            samples.length,
            this.audio.sampleRate
          );
          var channelLeft = buffer.getChannelData(0);
          var channelRight = buffer.getChannelData(1);
          // Convert from interleaved buffer format to planar buffer
          // by writing right into appropriate channel buffers
          var j = 0;
          for (var i = 0; i < samples.length; i += 2) {
            channelLeft[j] = this.intToFloatSample(samples[i]);
            channelRight[j] = this.intToFloatSample(samples[i + 1]);
            j++;
          }
          // Create sound source and play it
          var source = this.audio.createBufferSource();
          source.buffer = buffer;
          source.connect(this.audio.destination); // Output to sound
          // card
          source.start();
        },
        // Local helper function to convert Int output to Float
        // TODO: remove intToFloat and revise papu.js -> sample()
        //       to return AudioBuffer/Float32Array output used in HTML5 WebAudio API
        intToFloatSample: function (value) {
          return value / 32767; // from -32767..32768 to -1..1 range
        },

        writeFrame: function (buffer, prevBuffer) {
          var imageData = this.canvasImageData.data;
          var pixel, i, j;

          for (i = 0; i < 256 * 240; i++) {
            pixel = buffer[i];

            if (pixel != prevBuffer[i]) {
              j = i * 4;
              imageData[j] = pixel & 0xff;
              imageData[j + 1] = (pixel >> 8) & 0xff;
              imageData[j + 2] = (pixel >> 16) & 0xff;
              prevBuffer[i] = pixel;
            }
          }

          this.canvasContext.putImageData(this.canvasImageData, 0, 0);
        },
      };

      return UI;
    };
  })(jQuery);
}
